package engine

type ConcurrentEngine struct {
	Scheduler   Scheduler // 任务调度器
	WorkerCount int       // 任务工作者数量
	ItemChan    chan Item
}

type Scheduler interface {
	ReadyNotifier
	Submit(Request)
	WorkerChan() chan Request
	Run()
}

type ReadyNotifier interface {
	WorkerReady(chan Request)
}

func (e *ConcurrentEngine) Run(seeds ...Request) {
	out := make(chan ParseResult) // 接收解析结果的通道
	e.Scheduler.Run()

	for i := 0; i < e.WorkerCount; i++ {
		createWorker(e.Scheduler.WorkerChan(), out, e.Scheduler) // 创建任务工作者（发请求抓取网页数据，并解析）
	}

	for _, r := range seeds {
		if isDuplicate(r.Url) {
			continue
		}
		e.Scheduler.Submit(r) // 向调度器提交任务，由调度器往请求通道分发任务
	}

	for {
		result := <-out // 获取解析结果
		for _, item := range result.Items {
			go func() { e.ItemChan <- item }()
		}

		for _, request := range result.Requests {
			if isDuplicate(request.Url) { 	// url去重
				continue
			}
			e.Scheduler.Submit(request) 	// 将相关请求，提交给调度器，调度器再将任务分发到请求通道
		}
	}
}

// 任务工作者
func createWorker(in chan Request, out chan ParseResult, ready ReadyNotifier) {
	go func() {
		for {
			ready.WorkerReady(in)
			request := <-in
			result, err := worker(request) // 将请求送给worker去获取网页内容，并解析结果
			if err != nil {
				continue
			}
			out <- result // 将结果返回给out
		}
	}()
}

var visitedUrls = make(map[string]bool)

// url去重
// 扩展：
// 1. 程序结束时，将访问过的url保存到本地，下次启动的时候再加载到内存中
// 2. 可将url放redis或memcache缓存
// 注意：将url放到map中比较消耗内存，考虑使是md5、双重hash的方式减少内存占用
func isDuplicate(url string) bool {
	if visitedUrls[url] {
		return true
	}
	visitedUrls[url] = true
	return false
}
